﻿using Abp.Timing;
using FileHelpers;
using FluentFTP;
using Hicap.QRCodeWarehouse.Processor.Model;
using ICSharpCode.SharpZipLib.Zip;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace Hicap.QRCodeWarehouse.Processor.Manager
{
    /// <summary>
    /// Generate code repository
    /// </summary>
    public class CodeGenManager : ManagerBase, ICodeGenManager
    {
        private readonly FtpClient _ftpClient;
        private readonly IMongoClient _mongoClient;

        /// <summary>
        /// override GetCollection,create code unique index
        /// </summary>
        /// <param name="collectionName">string</param>
        /// <param name="indexNameList">IList&lt;Tuple&lt;string, CreateIndexOptions&gt;&gt;</param>
        /// <returns></returns>
        public override IMongoCollection<T> GetCollection<T>(string collectionName,
            IList<Tuple<string, CreateIndexOptions>> indexNameList = null)
        {
            //create index option
            List<Tuple<string, CreateIndexOptions>> createIndexOptions = new List<Tuple<string, CreateIndexOptions>>()
            {
                new Tuple<string, CreateIndexOptions>("Code", new CreateIndexOptions() {Unique = true, Name = "Code_1"})
            };
            //base getCollection
            return base.GetCollection<T>(collectionName, createIndexOptions);
        }

        public CodeGenManager(IMongoClient mongoClient, FtpClient ftpClient, IMongoClient mongoClient1) : base(mongoClient)
        {
            _ftpClient = ftpClient;
            _mongoClient = mongoClient1;
        }

        /// <summary>
        /// Insert many qrCodeRecords
        /// </summary>
        /// <param name="data">QrCodeRecord[]</param>
        /// <param name="collection">IMongoCollection&lt;QrCodeRecord&gt;</param>
        /// <param name="definitionFunc">Func&lt;T,Tuple&lt;FilterDefinition&lt;T&gt;, UpdateDefinition&lt;T&gt;&gt;&gt;</param>
        /// <param name="clientSession">IClientSessionHandle</param>
        /// <returns></returns>
        public async Task<bool> InsertRecordAsync<T>(T[] data, IMongoCollection<T> collection, Func<T, Tuple<FilterDefinition<T>, UpdateDefinition<T>>> definitionFunc, IClientSessionHandle clientSession)
        {
            List<UpdateOneModel<T>> bulkList = new List<UpdateOneModel<T>>();
            foreach (T qrCodeRecord in data)
            {
                Tuple<FilterDefinition<T>, UpdateDefinition<T>> definition = definitionFunc.Invoke(qrCodeRecord);

                UpdateOneModel<T> updateModel = new UpdateOneModel<T>(definition.Item1
                    , definition.Item2)
                { IsUpsert = true };

                bulkList.Add(updateModel);
            }
            //use mongodb session
            await collection.BulkWriteAsync(clientSession, bulkList);

            //            await collection.InsertManyAsync(clientSession, data);
            return true;
        }

        /// <summary>
        /// Exract data from data zip file
        /// </summary>
        /// <param name="input">ImportCommandDto</param>
        /// <param name="action">Action&lt;T&gt;</param>
        /// <returns>List&lt;QrCodeRecord&gt;</returns>
        public async Task<List<T>> ExractDataFromZip<T>(ImportCommandDto input, Action<T> action) where T : class
        {
            using (MemoryStream stream = new MemoryStream())
            {
                //result
                List<T> result = new List<T>();

                if (await _ftpClient.DownloadAsync(stream, input.FilePath))
                {
                    //open file and read stream
                    ZipFile zf = new ZipFile(stream);

                    //set password
                    if (!(string.IsNullOrEmpty(input.FilePassword)))
                        zf.Password = input.FilePassword;

                    //FileHelper read engine
                    FileHelperAsyncEngine<T> engine = new FileHelperAsyncEngine<T>();

                    await Task.Run(() =>
                    {
                        //loop load zip entry
                        foreach (ZipEntry zipEntry in zf)
                        {
                            if (!zipEntry.IsFile)
                            {
                                // Ignore directories
                                continue;
                            }

                            //get file stream
                            using (Stream zipStream = zf.GetInputStream(zipEntry))
                            {
                                //start read stream
                                using (engine.BeginReadStream(new StreamReader(zipStream)))
                                {
                                    //convert memory record to  qrCodeRecord
                                    foreach (T qrCodeRecord in engine)
                                    {
                                        action.Invoke(qrCodeRecord);
                                        result.Add(qrCodeRecord);
                                    }
                                }
                            }
                        }
                    });
                }

                return result;
            }
        }

        public async Task UpdateCodeLinked(List<QrCodeLinkedRecord> list, ImportCommandDto config)
        {
            //collection
            IMongoCollection<QrCodeRecord> innerCollection = base.GetCollection<QrCodeRecord>(string.Join('_', config.ProductCode, 0));

            IMongoCollection<QrCodeRecord> outerCollection = base.GetCollection<QrCodeRecord>(string.Join('_', config.ProductCode, 1));

            //update setting
            UpdateDefinition<QrCodeRecord> innerUpdate = Builders<QrCodeRecord>.Update
                .Set(x => x.IsUsed, true)
                .Set(x => x.UsedTime, Clock.Now);

            UpdateDefinition<QrCodeRecord> outerUpdate = Builders<QrCodeRecord>.Update
                .Set(x => x.IsUsed, true)
                .Set(x => x.UsedTime, Clock.Now);

            List<UpdateOneModel<QrCodeRecord>> innerBulkList = new List<UpdateOneModel<QrCodeRecord>>();

            List<UpdateOneModel<QrCodeRecord>> outerBulkList = new List<UpdateOneModel<QrCodeRecord>>();

            //update in session ctrl
            //loop update
            foreach (QrCodeLinkedRecord linkedData in list)
            {
                if (config.IsInnerTypeBePrimary)
                {
                    innerUpdate.Set(x => x.LinkedCode, linkedData.LinkedCode);
                    UpdateOneModel<QrCodeRecord> innerUpdateModel = new UpdateOneModel<QrCodeRecord>(Builders<QrCodeRecord>.Filter.Eq(y => y.Code, linkedData.Code), innerUpdate);
                    innerBulkList.Add(innerUpdateModel);

                    outerUpdate.Set(x => x.LinkedCode, linkedData.Code);
                    UpdateOneModel<QrCodeRecord> outerUpdateModel = new UpdateOneModel<QrCodeRecord>(Builders<QrCodeRecord>.Filter.Eq(y => y.Code, linkedData.LinkedCode), outerUpdate);
                    outerBulkList.Add(outerUpdateModel);
                }
                else
                {
                    innerUpdate.Set(x => x.LinkedCode, linkedData.Code);
                    UpdateOneModel<QrCodeRecord> innerUpdateModel = new UpdateOneModel<QrCodeRecord>
                        (Builders<QrCodeRecord>.Filter.Eq(y => y.Code, linkedData.LinkedCode), innerUpdate);
                    innerBulkList.Add(innerUpdateModel);

                    outerUpdate.Set(x => x.LinkedCode, linkedData.LinkedCode);
                    UpdateOneModel<QrCodeRecord> outerUpdateModel = new UpdateOneModel<QrCodeRecord>
                        (Builders<QrCodeRecord>.Filter.Eq(y => y.Code, linkedData.Code), outerUpdate);
                    outerBulkList.Add(outerUpdateModel);
                }
            }

            using (IClientSessionHandle clientSession = await _mongoClient.StartSessionAsync())
            {
                try
                {
                    clientSession.StartTransaction();

                    await Task.WhenAll(
                        innerCollection.BulkWriteAsync(clientSession, innerBulkList),
                        outerCollection.BulkWriteAsync(clientSession, outerBulkList));

                    await clientSession.CommitTransactionAsync();
                }
                catch
                {
                    await clientSession.AbortTransactionAsync();
                    throw;
                }
            }
        }
    }
}